最近比较忙,忙的都没空理csdn了,今天我继续迈着魔鬼般的步伐,摩擦摩擦……总结下最近学到的MongoDB的知识。
1.认识Spring Data MongoDB
之前还的确不知道Spring连集成Nosql的东西都实现了,还以为自己又要手动封装一个操作MongoDB的API呢,结果就发现了Spring Data MongoDB。不愧是Spring,真是给了我们春天,佩服的渣渣我目瞪狗呆……
废话少说,看招。Spring Data其实是一个高级别的Spring Source项目,而Spring Data MongoDB仅仅是其中的一个子项目。Spring Data旨在为关系型数据库、非关系型数据、Map-Reduce框架、云数据服务等等提供统一的数据访问API。
无论是哪种持久化存储, 数据访问对象(或称作为DAO,即Data Access Objects)通常都会提供对单一域对象的CRUD (创建、读取、更新、删除)操作、查询方法、排序和分页方法等。Spring Data则提供了基于这些层面的统一接口(CrudRepository,PagingAndSortingRepository)以及对持久化存储的实现。
Spring Data 包含多个子项目:
-
Commons - 提供共享的基础框架,适合各个子项目使用,支持跨数据库持久化
-
Hadoop - 基于 Spring 的 Hadoop 作业配置和一个 POJO 编程模型的 MapReduce 作业
-
Key-Value - 集成了 Redis 和 Riak ,提供多个常用场景下的简单封装
-
Document - 集成文档数据库:CouchDB 和 MongoDB 并提供基本的配置映射和资料库支持
-
Graph - 集成 Neo4j 提供强大的基于 POJO 的编程模型
-
Graph Roo AddOn - Roo support for Neo4j
-
JDBC Extensions - 支持 Oracle RAD、高级队列和高级数据类型
-
JPA - 简化创建 JPA 数据访问层和跨存储的持久层功能
-
Mapping - 基于 Grails 的提供对象映射框架,支持不同的数据库
-
Examples - 示例程序、文档和图数据库
-
Guidance - 高级文档
2. HelloWorld
好了,说了这么多,还是用代码表现最为实在,老规矩,Hello World,我这里创建了一个Maven Java项目demo。
2.1 配置依赖
【pom.xml】
注意:这里我使用的是Spring Data MongoDB 1.7.0版本,截止当前发文时间最新稳定版本应该是1.10.1
2.2 映射实体类
【UserInfo.java】
- package com.jastar.demo.entity;
-
- import java.io.Serializable;
- import java.sql.Timestamp;
-
- import org.springframework.data.annotation.Id;
- import org.springframework.data.mongodb.core.index.IndexDirection;
- import org.springframework.data.mongodb.core.index.Indexed;
- import org.springframework.data.mongodb.core.mapping.Document;
- import org.springframework.data.mongodb.core.mapping.Field;
-
- /**
- * 用户实体类
- * <p>
- * ClassName: UserInfo
- * </p>
- * <p>
- * Description:本类用来展示MongoDB实体类映射的使用
- * </p>
- * <p>
- * Copyright: (c)2017 Jastar·Wang,All rights reserved.
- * </p>
- *
- * @author Jastar·Wang
- * @date 2017年4月12日
- */
- @Document(collection = "coll_user")
- public class UserInfo implements Serializable {
-
- /** serialVersionUID */
- private static final long serialVersionUID = 1L;
-
- // 主键使用此注解
- @Id
- private String id;
-
- // 字段使用此注解
- @Field
- private String name;
-
- // 字段还可以用自定义名称
- @Field("myage")
- private int age;
-
- // 还可以生成索引
- @Indexed(name = "index_birth", direction = IndexDirection.DESCENDING)
- @Field
- private Timestamp birth;
-
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public int getAge() {
- return age;
- }
-
- public void setAge(int age) {
- this.age = age;
- }
-
- public Timestamp getBirth() {
- return birth;
- }
-
- public void setBirth(Timestamp birth) {
- this.birth = birth;
- }
-
- }
附录:
-
@Id - 用于字段级别,标记这个字段是一个主键,默认生成的名称是“_id”
-
@Document - 用于类,以表示这个类需要映射到数据库,您也可以指定映射到数据库的集合名称
-
@DBRef - 用于字段,以表示它将使用com.mongodb.DBRef进行存储。
-
@Indexed - 用于字段,表示该字段需要如何创建索引
-
@CompoundIndex - 用于类,以声明复合索引
-
@GeoSpatialIndexed - 用于字段,进行地理位置索引
-
@TextIndexed - 用于字段,标记该字段要包含在文本索引中
-
@Language - 用于字段,以设置文本索引的语言覆盖属性。
-
@Transient - 默认情况下,所有私有字段都映射到文档,此注解将会去除此字段的映射
-
@PersistenceConstructor - 标记一个给定的构造函数,即使是一个protected修饰的,在从数据库实例化对象时使用。构造函数参数通过名称映射到检索的DBObject中的键值。
-
@Value - 这个注解是Spring框架的一部分。在映射框架内,它可以应用于构造函数参数。这允许您使用Spring表达式语言语句来转换在数据库中检索的键值,然后再用它来构造一个域对象。为了引用给定文档的属性,必须使用以下表达式:@Value("#root.myProperty"),root要指向给定文档的根。
-
@Field - 用于字段,并描述字段的名称,因为它将在MongoDB BSON文档中表示,允许名称与该类的字段名不同。
-
@Version - 用于字段锁定,保存操作时检查修改。初始值是0,每次更新时自动触发。
2.3 整合配置
【db.properties】
- ###---The mongodb settings---
- mongo.dbname=demo
- mongo.host=localhost
- mongo.port=27017
- mongo.connectionsPerHost=8
- mongo.threadsAllowedToBlockForConnectionMultiplier=4
- mongo.connectTimeout=1000
- mongo.maxWaitTime=1500
- mongo.autoConnectRetry=true
- mongo.socketKeepAlive=true
- mongo.socketTimeout=1500
- mongo.slaveOk=true
- mongo.writeNumber=1
- mongo.writeTimeout=0
- mongo.writeFsync=true
【spring-mgo.xml】
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:mongo="http://www.springframework.org/schema/data/mongo"
- xsi:schemaLocation="http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-3.0.xsd
- http://www.springframework.org/schema/data/mongo
- http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd
- http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
- <!-- 读取属性文件 -->
- <context:property-placeholder location="classpath:db.properties" />
-
- <!-- 启用注解支持 -->
- <context:annotation-config />
-
- <!-- 扫描组件包 -->
- <context:component-scan base-package="com.jastar.demo" />
-
- <!-- SpringData类型转换器 -->
- <mongo:mapping-converter id="mongoConverter">
- <mongo:custom-converters>
- <mongo:converter>
- <bean class="com.jastar.demo.converter.TimestampConverter" />
- </mongo:converter>
- </mongo:custom-converters>
- </mongo:mapping-converter>
-
- <!--
- MongoDB配置部分
- 1.mongo:连接配置
- 2.db-factory:相当于sessionFactory
- 3.mongoTemplate:与数据库接口交互的主要实现类
- -->
- <mongo:mongo host="${mongo.host}" port="${mongo.port}">
- <mongo:options
- connections-per-host="${mongo.connectionsPerHost}"
- threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}"
- connect-timeout="${mongo.connectTimeout}"
- max-wait-time="${mongo.maxWaitTime}"
- auto-connect-retry="${mongo.autoConnectRetry}"
- socket-keep-alive="${mongo.socketKeepAlive}"
- socket-timeout="${mongo.socketTimeout}"
- slave-ok="${mongo.slaveOk}"
- write-number="${mongo.writeNumber}"
- write-timeout="${mongo.writeTimeout}"
- write-fsync="${mongo.writeFsync}" />
- </mongo:mongo>
-
- <mongo:db-factory id="mongoDbFactory" dbname="${mongo.dbname}" mongo-ref="mongo" />
-
- <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
- <constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
- <constructor-arg name="mongoConverter" ref="mongoConverter" />
- </bean>
-
- </beans>
说明:
mongo:options - 用于配置一些数据库连接设置信息
mongo:db-factory - 相当于Hibernate中的SessionFactory
mongoTemplate - 非常重要,整个与数据库的交互操作全是靠他,相当于Hibernate的HibernateTemplate
另外,以上配置中有一个类型转换器,因为Spring Data MongoDB本身默认时间类型是java.util.Date,如果实体字段含有java.sql.Timestamp类型,需要自定义转换器进行转换,否则后续操作会报错(深有感触)!什么?不把转换器代码贴出来?别着急,文章最后有整个项目地址,去里面找吧……偷笑.GIF
2.4 Dao层参考实现
【BaseDaoImpl.java】
- package com.jastar.demo.dao.impl;
-
- import static org.springframework.data.mongodb.core.query.Criteria.where;
-
- import java.io.Serializable;
- import java.lang.reflect.Field;
- import java.lang.reflect.Method;
- import java.lang.reflect.Modifier;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.data.annotation.Id;
- import org.springframework.data.domain.Sort;
- import org.springframework.data.domain.Sort.Direction;
- import org.springframework.data.domain.Sort.Order;
- import org.springframework.data.mongodb.core.MongoTemplate;
- import org.springframework.data.mongodb.core.query.Query;
- import org.springframework.data.mongodb.core.query.Update;
-
- import com.jastar.demo.dao.IBaseDao;
- import com.jastar.demo.util.EmptyUtil;
- import com.jastar.demo.util.PageModel;
-
- /**
- * 基本操作接口MongoDB数据库实现类
- * <p>
- * ClassName: BaseDaoImpl
- * </p>
- * <p>
- * Description:本实现类适用于MongoDB数据库,以下代码仅供参考,本人水平有限,可能会存在些许问题(如有更好方案可告知我,一定虚心学习),
- * 再次提醒,仅供参考!!
- * </p>
- * <p>
- * Copyright: (c)2017 Jastar·Wang,All rights reserved.
- * </p>
- *
- * @author Jastar·Wang
- * @date 2017年4月12日
- */
- public abstract class BaseDaoImpl<T> implements IBaseDao<T> {
-
- protected abstract Class<T> getEntityClass();
-
- @Autowired
- protected MongoTemplate mgt;
-
- @Override
- public void save(T entity) {
- mgt.save(entity);
- }
-
- @Override
- public void update(T entity) {
-
- // 反向解析对象
- Map<String, Object> map = null;
- try {
- map = parseEntity(entity);
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- // ID字段
- String idName = null;
- Object idValue = null;
-
- // 生成参数
- Update update = new Update();
- if (EmptyUtil.isNotEmpty(map)) {
- for (String key : map.keySet()) {
- if (key.indexOf("{") != -1) {
- // 设置ID
- idName = key.substring(key.indexOf("{") + 1, key.indexOf("}"));
- idValue = map.get(key);
- } else {
- update.set(key, map.get(key));
- }
- }
- }
- mgt.updateFirst(new Query().addCriteria(where(idName).is(idValue)), update, getEntityClass());
- }
-
- @Override
- public void delete(Serializable... ids) {
- if (EmptyUtil.isNotEmpty(ids)) {
- for (Serializable id : ids) {
- mgt.remove(mgt.findById(id, getEntityClass()));
- }
- }
-
- }
-
- @Override
- public T find(Serializable id) {
- return mgt.findById(id, getEntityClass());
- }
-
- @Override
- public List<T> findAll() {
- return mgt.findAll(getEntityClass());
- }
-
- @Override
- public List<T> findAll(String order) {
- List<Order> orderList = parseOrder(order);
- if (EmptyUtil.isEmpty(orderList)) {
- return findAll();
- }
- return mgt.find(new Query().with(new Sort(orderList)), getEntityClass());
- }
-
- @Override
- public List<T> findByProp(String propName, Object propValue) {
- return findByProp(propName, propValue, null);
- }
-
- @Override
- public List<T> findByProp(String propName, Object propValue, String order) {
- Query query = new Query();
- // 参数
- query.addCriteria(where(propName).is(propValue));
- // 排序
- List<Order> orderList = parseOrder(order);
- if (EmptyUtil.isNotEmpty(orderList)) {
- query.with(new Sort(orderList));
- }
- return mgt.find(query, getEntityClass());
- }
-
- @Override
- public List<T> findByProps(String[] propName, Object[] propValue) {
- return findByProps(propName, propValue, null);
- }
-
- @Override
- public List<T> findByProps(String[] propName, Object[] propValue, String order) {
- Query query = createQuery(propName, propValue, order);
- return mgt.find(query, getEntityClass());
- }
-
- @Override
- public T uniqueByProp(String propName, Object propValue) {
- return mgt.findOne(new Query(where(propName).is(propValue)), getEntityClass());
- }
-
- @Override
- public T uniqueByProps(String[] propName, Object[] propValue) {
- Query query = createQuery(propName, propValue, null);
- return mgt.findOne(query, getEntityClass());
- }
-
- @Override
- public PageModel<T> pageAll(int pageNo, int pageSize) {
- return pageAll(pageNo, pageSize, null);
- }
-
- @Override
- public PageModel<T> pageAll(int pageNo, int pageSize, String order) {
- return pageByProp(pageNo, pageSize, null, null, order);
- }
-
- @Override
- public PageModel<T> pageByProp(int pageNo, int pageSize, String param, Object value) {
- return pageByProp(pageNo, pageSize, param, value, null);
- }
-
- @Override
- public PageModel<T> pageByProp(int pageNo, int pageSize, String param, Object value, String order) {
- String[] params = null;
- Object[] values = null;
- if (EmptyUtil.isNotEmpty(param)) {
- params = new String[] { param };
- values = new Object[] { value };
- }
- return pageByProps(pageNo, pageSize, params, values, order);
- }
-
- @Override
- public PageModel<T> pageByProps(int pageNo, int pageSize, String[] params, Object[] values) {
- return pageByProps(pageNo, pageSize, params, values, null);
- }
-
- @Override
- public PageModel<T> pageByProps(int pageNo, int pageSize, String[] params, Object[] values, String order) {
- // 创建分页模型对象
- PageModel<T> page = new PageModel<>(pageNo, pageSize);
-
- // 查询总记录数
- int count = countByCondition(params, values);
- page.setTotalCount(count);
-
- // 查询数据列表
- Query query = createQuery(params, values, order);
-
- // 设置分页信息
- query.skip(page.getFirstResult());
- query.limit(page.getPageSize());
-
- // 封装结果数据
- page.setList(mgt.find(query, getEntityClass()));
-
- return page;
- }
-
- @Override
- public int countByCondition(String[] params, Object[] values) {
- Query query = createQuery(params, values, null);
- Long count = mgt.count(query, getEntityClass());
- return count.intValue();
- }
-
- /**
- * 创建带有where条件(只支持等值)和排序的Query对象
- *
- * @param params
- * 参数数组
- * @param values
- * 参数值数组
- * @param order
- * 排序
- * @return Query对象
- */
- protected Query createQuery(String[] params, Object[] values, String order) {
- Query query = new Query();
-
- // where 条件
- if (EmptyUtil.isNotEmpty(params) && EmptyUtil.isNotEmpty(values)) {
- for (int i = 0; i < params.length; i++) {
- query.addCriteria(where(params[i]).is(values[i]));
- }
- }
-
- // 排序
- List<Order> orderList = parseOrder(order);
- if (EmptyUtil.isNotEmpty(orderList)) {
- query.with(new Sort(orderList));
- }
-
- return query;
- }
-
- /**
- * 解析Order字符串为所需参数
- *
- * @param order
- * 排序参数,如[id]、[id asc]、[id asc,name desc]
- * @return Order对象集合
- */
- protected List<Order> parseOrder(String order) {
- List<Order> list = null;
- if (EmptyUtil.isNotEmpty(order)) {
- list = new ArrayList<Order>();
- // 共有几组排序字段
- String[] fields = order.split(",");
- Order o = null;
- String[] item = null;
- for (int i = 0; i < fields.length; i++) {
- if (EmptyUtil.isEmpty(fields[i])) {
- continue;
- }
- item = fields[i].split(" ");
- if (item.length == 1) {
- o = new Order(Direction.ASC, item[0]);
- } else if (item.length == 2) {
- o = new Order("desc".equalsIgnoreCase(item[1]) ? Direction.DESC : Direction.ASC, item[0]);
- } else {
- throw new RuntimeException("排序字段参数解析出错");
- }
- list.add(o);
- }
- }
- return list;
- }
-
- /**
- * 将对象的字段及值反射解析为Map对象<br>
- * 这里使用Java反射机制手动解析,并且可以识别注解为主键的字段,以达到根据id进行更新实体的目的<br>
- * key:字段名称,value:字段对应的值
- *
- * @param t
- * 要修改的对象
- * @return Map对象,注意:id字段的key封装为“{id字段名称}”,以供后续识别
- * @throws Exception
- */
- protected Map<String, Object> parseEntity(T t) throws Exception {
- Map<String, Object> map = new HashMap<String, Object>();
- /*
- * 解析ID
- */
- String idName = "";
- Field[] declaredFields = getEntityClass().getDeclaredFields();
- for (Field field : declaredFields) {
- if (field.isAnnotationPresent(Id.class)) {
- field.setAccessible(true);
- map.put("{" + field.getName() + "}", field.get(t));
- idName = field.getName();
- break;
- }
- }
- /*
- * 解析其他属性
- */
- Method[] methods = getEntityClass().getDeclaredMethods();
- if (EmptyUtil.isNotEmpty(methods)) {
- for (Method method : methods) {
- if (method.getName().startsWith("get") && method.getModifiers() == Modifier.PUBLIC) {
- String fieldName = parse2FieldName(method.getName());
- if (!fieldName.equals(idName)) {
- map.put(fieldName, method.invoke(t));
- }
- }
- }
- }
-
- return map;
- }
-
- /**
- * 将get方法名转换为对应的字段名称
- *
- * @param methodName
- * 如:getName
- * @return 如:name
- */
- private String parse2FieldName(String methodName) {
- String name = methodName.replace("get", "");
- name = name.substring(0, 1).toLowerCase() + name.substring(1);
- return name;
- }
-
- }
说明:
以上实现仅供参考某些实现点,mongoTemplate如何使用还要自己去详细的摸索,我也没办法,毕竟我也这样过来的,大多数方法都已经提供了,只是update方法不像关系型数据库那样,给个实体类就能更新,需要自己去想办法搞定。
【TestUseService.java】
- package com.jastar.test;
-
- import java.sql.Timestamp;
- import java.util.List;
-
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.test.context.ContextConfiguration;
- import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-
- import com.jastar.demo.entity.UserInfo;
- import com.jastar.demo.service.UserService;
- import com.jastar.demo.util.PageModel;
-
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration(locations = "classpath:spring-mgo.xml")
- public class TestUserService {
-
- @Autowired
- private UserService service;
-
- @Test
- public void save() {
- UserInfo user = new UserInfo();
- user.setName("张三");
- user.setAge(25);
- user.setBirth(Timestamp.valueOf("2017-4-12 16:52:00"));
- service.save(user);
- System.out.println("已生成ID:" + user.getId());
- }
-
- @Test
- public void find() {
- UserInfo user = service.find("58edf1b26f033406394a8a61");
- System.out.println(user.getName());
- }
-
- @Test
- public void update() {
- UserInfo user = service.find("58edf1b26f033406394a8a61");
- user.setAge(18);
- service.update(user);
- }
-
- @Test
- public void delete() {
- service.delete("58edef886f03c7b0fdba51b9");
- }
-
- @Test
- public void findAll() {
- List<UserInfo> list = service.findAll("age desc");
- for (UserInfo u : list) {
- System.out.println(u.getName());
- }
- }
-
- @Test
- public void findByProp() {
- List<UserInfo> list = service.findByProp("name", "张三");
- for (UserInfo u : list) {
- System.out.println(u.getName());
- }
- }
-
- @Test
- public void findByProps() {
- List<UserInfo> list = service.findByProps(new String[] { "name", "age" }, new Object[] { "张三", 18 });
- for (UserInfo u : list) {
- System.out.println(u.getName());
- }
- }
-
- @Test
- public void pageAll() {
- PageModel<UserInfo> page = service.pageAll(1, 10);
- System.out.println("总记录:" + page.getTotalCount() + ",总页数:" + page.getTotalPage());
- for (UserInfo u : page.getList()) {
- System.out.println(u.getName());
- }
- }
-
- @Test
- public void pageByProp() {
- PageModel<UserInfo> page = service.pageByProp(1, 10, "name", "张三");
- System.out.println("总记录:" + page.getTotalCount() + ",总页数:" + page.getTotalPage());
- for (UserInfo u : page.getList()) {
- System.out.println(u.getName());
- }
- }
-
- }
更多知识,请参考官方文档:戳我戳我
更多代码,请参考本项目Git 地址:戳我戳我